package 网络编程;

import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/*
     Socket
         利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准
         网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字
         通信的两端都要有Socket,是两台机器间通信的端点
         网络通信其实就是Socket间的通信
         Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输
         一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
         Socket分类
               流套接字(stream socket):使用TCP提供可依赖的字节流服务
               数据报套接字(datagram socket):使用UDP提供"尽力而为"的数据报服务

        常用构造器
             public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP地址的指定端口号
             public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号
        常用方法
             public InputStream getInputStream():返回此套接字的输入流,可以用于接收网络消息
             public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送网络消息
             public InetAddress getInetAddress():此套接字连接到的远程 IP 地址,如果套接字是未连接的,则返回 null
             public InetAddress getLocalAddress():获取套接字绑定的本地地址,即本端的IP地址
             public int getPort():此套接字连接到的远程端口号,如果尚未连接套接字,则返回0
             public int getLocalPort():返回此套接字绑定到的本地端口,如果尚未绑定套接字,则返回 -1,即本端的端口号
             public void close():关闭此套接字,套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定),需要创建新的套接字对象,关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream
             public void shutdownInput():如果在套接字上调用 shutdownInput()后从套接字输入流读取内容,则流将返回 EOF(文件结束符),即不能在从此套接字的输入流中接收任何数据
             public void shutdownOutput():禁用此套接字的输出流,对于 TCP02 套接字,任何以前写入的数据都将被发送,并且后跟 TCP的正常连接终止序列,如果在套接字上调用 shutdownOutput()后写入套接字输出流,则该流将抛出 IOException,即不能通过此套接字的输出流发送任何数据

     TCP网络编程
        基于Socket的TCP编程
             客户端Socket的工作过程包含以下四个基本的步骤
                1、创建 Socket:根据指定服务端的 IP地址或端口号构造 Socket类对象,若服务器端响应,则建立客户端到服务器的通信线路,若连接失败,会出现异常
                2、打开连接到 Socket的输入/出流:使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输
                3、按照一定的协议对 Socket进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程
                4、关闭 Socket:断开客户端到服务器的连接,释放线路
        客户端创建Socket对象
             客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连接,Socket的构造器是:
                 Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是 host,端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常
                 Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的 IP地址以及端口号port发起连接
             客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求

             服务器程序的工作过程包含以下四个基本的步骤：
                 调用 ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上,用于监听客户端的请求
                 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象
                 调用该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收
                 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字

         服务器建立 ServerSocket对象
               ServerSocket对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员,也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象
               所谓"接收"客户的套接字请求,就是accept()方法会返回一个 Socket对象
 */
public class TCP02 { //客户端发送内容给服务端,服务端将内容打印到控制台上

    //客户端
    @Test
    public void client(){
        Socket socket = null;
        OutputStream outputStream = null;
        try {
            //创建Socket对象,指明服务器端的IP和端口号
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); //指定IP地址
            socket = new Socket(inetAddress,8899); //根据IP地址和端口号创建Socket

            //获取输出流,用于输出数据
            outputStream = socket.getOutputStream();
            outputStream.write("养天地正气,法古今完人".getBytes());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //服务端
    @Test
    public void server(){
        ServerSocket serverSocket = null;
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        Socket accept = null;

        try {
            //创建服务器端的ServerSocket,并指明自己的端口号
            serverSocket = new ServerSocket(8899);

            //调用accept()方法,接收来自客户端的Socket
            accept = serverSocket.accept();
            //获取输入流
            inputStream = accept.getInputStream();

            //解析数据
            byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] array = new byte[1024];
            int len;
            while((len = inputStream.read(array)) != -1){
                byteArrayOutputStream.write(array,0,len);
            }

            //转为字符串输出
            System.out.println(byteArrayOutputStream.toString());

            //获取客户端相关信息
            System.out.println(accept.getInetAddress().getHostAddress());
            System.out.println(accept.getInetAddress().getHostName());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (byteArrayOutputStream != null){
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(accept != null){
                try {
                    accept.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
